package myutil.concurrency;

import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 *
 *
 * @author Hlib_Babii
 *
 */
public class ConcurrentCircularBufferQueue {

    private int[] buffer = null;

    private int tail = 0;
    private int head = 0;
    private boolean empty = true;
    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();

    public ConcurrentCircularBufferQueue(int size) {
        buffer = new int[size];
    }

    /**
     *
     * Inserts the specified element into this queue, waiting if necessary for
     * space to become available.
     *
     * @param e the element to add
     *
     */
    public void put(int e) {
        lock.lock();
        try {
            while (head == tail && !empty) {
                System.out.println("Queue is full...");
                try {
                    notFull.await();
                } catch (InterruptedException ex) {}
            }
            buffer[tail++] = e;
            if (tail == buffer.length) {
                tail = 0;
            }
            empty = false;
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
    * Retrieves and removes the head of this queue, waiting if necessary
    * until an element becomes available.
    *
    * @return the head of the queue
    */
    public int take() {
        int toReturn;
        lock.lock();
        try {
            while (empty) {
                System.out.println("Queue is empty...");
                try {
                    notEmpty.await();
                } catch (InterruptedException ex) {}
            }
            toReturn = buffer[head++];
            if (head == buffer.length) {
                head = 0;
            }
            if (head == tail) {
                empty = true;
            }
            notFull.signalAll();
        } finally {
            lock.unlock();
        }
        return toReturn;
    }

    public static class Producer implements Runnable {

        final Random random = new Random();
        ConcurrentCircularBufferQueue queue;

        Producer(ConcurrentCircularBufferQueue queue) {
            this.queue = queue;
        }
//        ArrayBlockingQueue<Integer> queue;
//
//        Producer(ArrayBlockingQueue<Integer> queue) {
//            this.queue = queue;
//        }

        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                queue.put(i);
                //Thread.sleep(random.nextInt(500));
            }
        }
    }

    public static class Consumer implements Runnable {

        final Random random = new Random();
        ConcurrentCircularBufferQueue queue;

        Consumer(ConcurrentCircularBufferQueue queue) {
            this.queue = queue;
        }
//        ArrayBlockingQueue<Integer> queue;
//
//        Consumer(ArrayBlockingQueue<Integer> queue) {
//            this.queue = queue;
//        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                System.out.println(queue.take());
                //Thread.sleep(random.nextInt(500));
            }
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
    }

    public static void main(String[] args) {
        ConcurrentCircularBufferQueue queue = new ConcurrentCircularBufferQueue(10);
        //ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
        //new Thread(new Consumer(queue)).start();
        //new Thread(new Consumer(queue)).start();
        long start = System.currentTimeMillis();
        Thread consumerThread = new Thread(new Consumer(queue));
        consumerThread.start();
        new Thread(new Producer(queue)).start();
        try {
            consumerThread.join();
        } catch (InterruptedException e) {}
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}
